

#include "LogicGraph.h"
#include <chrono>
#include <iostream>



class TimerBlock {
	std::chrono::high_resolution_clock::time_point start;
	const char *str;
public:
	TimerBlock(const char *str)
		: start(std::chrono::high_resolution_clock::now()), str(str) {
		std::cout << str << " start... \n";
	}
	~TimerBlock() {
		auto end = std::chrono::high_resolution_clock::now();
		std::cout << str << " finished, time used: "
			<< std::chrono::duration_cast<std::chrono::milliseconds>(end -
				start)
			.count()
			<< "ms\n\n";
	}
};

struct location { float x, y; };
struct velocity { float x, y; };

class Object
{
public:
	virtual void update() {};
};

class MovingObject : public Object
{
	location loc{ 0, 0 };
	velocity vel{ 1, 1 };
public:
	void update() override
	{
		for (int i = 0; i < 100; i++)
		{
			loc.x *= vel.x;
			loc.y *= vel.y;
		}
	}
};


class Actor;
class Component : public Object
{
protected:
	Actor * owner;
	friend class Actor;
};

class Actor : public Object
{
	std::unordered_map<size_t, Component*> components;
public:
	template<typename T>
	T* GetComponent()
	{
		auto i = components.find(typeid(T).hash_code());
		if (i != components.end())
			return dynamic_cast<T*>((*i).second);
		else
			return nullptr;
	}
	template<typename T>
	void AddComponent()
	{
		T* comp = new T{};
		comp->owner = this;
		components.insert({ typeid(T).hash_code(), comp });
	}

	void update() override
	{
		for (auto &i : components)
		{
			i.second->update();
		}
	}
};

class LocationComponent : public Component
{
public:
	location loc{ 0, 0 };
};

class VelocityComponent : public Component
{
	velocity vel{ 1, 1 };
public:
	void update() override
	{
		if (auto locComp = owner->GetComponent<LocationComponent>())
		{
			auto& loc = locComp->loc;
			auto _vel = vel;
			auto _loc = loc;
			for (int i = 0; i < 100; i++)
			{
				_loc.x *= _vel.x;
				_loc.y *= _vel.y;
			}
			loc = _loc;
		}
	}
};

void BenchMark_PureECS()
{
	using States = EntityState::StateManager<location, velocity>;
	States states;
	{
		TimerBlock timer("create 10m entity");
		for (size_t i = 0u; i < 10'000'000; i++)
		{
			auto entity = states.create_entity();
			entity.add(location{ 0, 0 });
			entity.add(velocity{ 1, 1 });
		}
	}
	auto move = [](const velocity& vel, location& loc)
	{
		//
		auto _vel = vel; 
		auto _loc = loc;
		for (int i = 0; i < 100; i++)
		{
			_loc.x *= _vel.x;
			_loc.y *= _vel.y;
		}
		loc = _loc;
	};
	{
		TimerBlock timer("move 10m entity");
		states->*move;
	}
}

void BenchMark_ESL()
{
	using States = EntityState::StateManager<location, velocity>;
	States states;
	{
		TimerBlock timer("create 10m entity");
		for (size_t i = 0u; i < 10'000'000; i++)
		{
			auto entity = states.create_entity();
			entity.add(location{ 0, 0 });
			entity.add(velocity{ 1, 1 });
		}
	}
	auto move = [](const velocity& vel, location& loc)
	{
		auto _vel = vel;
		auto _loc = loc;
		for (int i = 0; i < 100; i++)
		{
			_loc.x *= _vel.x;
			_loc.y *= _vel.y;
		}
		loc = _loc;
	};
	LogicGraph::Pipeline<States> pipeline;
	pipeline >> move;
	{
		TimerBlock timer("move 10m entity");
			pipeline(states);
	}
}

void BenchMark_PureOOP()
{
	std::vector<Object*> objects;
	{
		TimerBlock timer("create 10m entity");
		for (size_t i = 0u; i < 10'000'000; i++)
		{
			objects.emplace_back(new MovingObject);
		}
	}
	{
		TimerBlock timer("move 10m entity");
			for (auto &o : objects)
			{
				o->update();
			}
	}
}

void BenchMark_ActorComponent()
{
	std::vector<Object*> objects;
	{
		TimerBlock timer("create 10m entity");
		for (size_t i = 0u; i < 10'000'000; i++)
		{
			Actor* movingActor = new Actor;
			movingActor->AddComponent<LocationComponent>();
			movingActor->AddComponent<VelocityComponent>();
			objects.emplace_back(movingActor);
		}
	}

	{
		TimerBlock timer("move 10m entity");
			for (auto &o : objects)
			{
				o->update();
			}
	}
}


int main() {
	std::cout << "PureECS:\n";
	BenchMark_PureECS();
	std::cout << "\nESL:\n";
	BenchMark_ESL();
	std::cout << "\nPureOOP:\n";
	BenchMark_PureOOP();
	std::cout << "\nActorComponent:\n";
	BenchMark_ActorComponent();
	getchar();
	return 0;
}